package test.threads.queue.common;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * Потокобезопасный конечный автомат
 *
 * UNINITIALIZED    initialize()     -> BEFORE_RUN
 *
 * BEFORE_WAIT      autoTransition() -> WAITING
 * BEFORE_RUN       autoTransition() -> RUNNING
 * BEFORE_TERMINATE autoTransition() -> TERMINATING
 *
 * RUNNING          pause()          -> BEFORE_WAIT
 * RUNNING          terminate()      -> BEFORE_TERMINATE
 *
 * WAITING          start()          -> BEFORE_RUN
 * WAITING          terminate()      -> BEFORE_TERMINATE
 *
 * TERMINATING (final state)
 *
 * @author cheremin
 * @since 24.03.12,  14:29
 */
public class FSM {
    public enum State {
        UNINITIALIZED,

        BEFORE_RUN,
        RUNNING,

        BEFORE_WAIT,
        WAITING,

        BEFORE_TERMINATE,
        TERMINATING
    }

    private static final State[] STATES = State.values();

    private static final AtomicIntegerFieldUpdater<FSM> UPDATER = AtomicIntegerFieldUpdater.newUpdater(
            FSM.class,
            "state"
    );

    public volatile long $p1, $p2, $p3, $p4, $p5, $p6 = 7L;

    private volatile int state = State.UNINITIALIZED.ordinal();

    public volatile long $p7, $p8, $p9, $p10, $p11, $p12 = 7L;

    private boolean CAS( final State expected,
                         final State changeTo ) {
        return UPDATER.compareAndSet( this, expected.ordinal(), changeTo.ordinal() );
    }

    private void changeState( final State expected,
                              final State changeTo ) throws IllegalStateException {
        if ( !CAS( expected, changeTo ) ) {
            throw new IllegalStateException( "Unexpected state " + state() + " for transition (" + expected + " -> " + changeTo + ")" );
        }
//          log.debug( "transfer( " + expected + " -> " + changeTo + " )" );
    }

    public State autoTransition() {
        final State s = state();
        switch ( s ) {
            case BEFORE_WAIT:
                changeState( State.BEFORE_WAIT, State.WAITING );
                return State.WAITING;
            case BEFORE_RUN:
                changeState( State.BEFORE_RUN, State.RUNNING );
                return State.RUNNING;
            case BEFORE_TERMINATE:
                changeState( State.BEFORE_TERMINATE, State.TERMINATING );
                return State.TERMINATING;
            default:
                return s;
        }
    }

    public boolean is( final State expected ) {
        return stateOrdinal() == expected.ordinal();
    }

    public State state() {
        return STATES[this.state];
    }

    public int stateOrdinal() {
        return this.state;
    }

    public void waitForState( final State expected ) {
        while ( stateOrdinal() != expected.ordinal() ) {
            Thread.yield();
        }
    }

    public void initialize() {
        changeState( State.UNINITIALIZED, State.BEFORE_WAIT );
    }

    public void start() {
        changeState( State.WAITING, State.BEFORE_RUN );
//            waitForState( State.RUNNING );
    }

    public void pause() {
        changeState( State.RUNNING, State.BEFORE_WAIT );
//            waitForState( State.WAITING );
    }

    public void terminate() {
        final State s = state();
        switch ( s ) {
            case WAITING:
            case RUNNING:
                changeState( s, State.BEFORE_TERMINATE );
                break;

            case BEFORE_TERMINATE:
                break;//nothing to do here

//                case BEFORE_WAIT:
//                case BEFORE_RUN:
            default:
                throw new IllegalStateException( "Unexpected state " + s + " for terminate()" );
        }
        waitForState( State.TERMINATING );
    }
}
